// 版权所有2011 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 包csv读取和写入逗号分隔值（csv）文件。
// CSV文件种类繁多；该软件包支持RFC 4180中描述的
// 格式。
// 
// 一个csv文件包含零个或多个记录，每个记录包含一个或多个字段。
// 每条记录用换行符分隔。最后一条记录可以后跟换行符。
// 
// field1、field2、field3 
// 
// 空格被视为字段的一部分。
// 
// 换行符被自动删除前回车。
// 
// 忽略空行。只有空格字符的行（不包括
// 结尾换行符）不被视为空行。
// 
// 以引号字符开头和结尾的字段称为
// 带引号的字段。开头和结尾引号不是
// 字段的一部分。
// 
// 源代码：
// 
// 普通字符串，带引号的字段“
// 
// 字段中的结果
// 
// {normal string`、`quoted field`}
// 
// 在一个带引号的字段中，一个带引号的字符后跟一个带引号的
// 字符被认为是一个单引号。
// 
// 结果
// 
// /{‘单词’是真的，`a“引用字段”`}
// 
// 在引用字段
// 
// ，“
// 
// 结果
// 
// /{`多行
// 字段'，'逗号是，'}”
package csv

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"io"
	"unicode"
	"unicode/utf8"
)

// 对于解析错误，将返回ParseError。
// 行号为1索引，列为0索引。
type ParseError struct {
	StartLine int   // 记录开始的行
	Line      int   // 错误发生的行
	Column    int   // 错误发生的列（基于1的字节索引）
	Err       error // 实际错误
}

func (e *ParseError) Error() string {
	if e.Err == ErrFieldCount {
		return fmt.Sprintf("record on line %d: %v", e.Line, e.Err)
	}
	if e.StartLine != e.Line {
		return fmt.Sprintf("record on line %d; parse error on line %d, column %d: %v", e.StartLine, e.Line, e.Column, e.Err)
	}
	return fmt.Sprintf("parse error on line %d, column %d: %v", e.Line, e.Column, e.Err)
}

func (e *ParseError) Unwrap() error { return e.Err }

// 这些是可以在ParseError中返回的错误。犯错误
var (
	ErrTrailingComma = errors.New("extra delimiter at end of line") // 已弃用：不再使用。
	ErrBareQuote     = errors.New("bare \" in non-quoted-field")
	ErrQuote         = errors.New("extraneous or missing \" in quoted-field")
	ErrFieldCount    = errors.New("wrong number of fields")
)

var errInvalidDelim = errors.New("csv: invalid field or comment delimiter")

func validDelim(r rune) bool {
	return r != 0 && r != '"' && r != '\r' && r != '\n' && utf8.ValidRune(r) && r != utf8.RuneError
}

// 读卡器从CSV编码的文件中读取记录。
// 
// 正如NewReader返回的，读卡器希望输入符合RFC 4180。
// 导出的字段可以在
// 第一次调用Read或ReadAll之前进行更改，以自定义详细信息。
// 
// 读取器将其输入中的所有\r\n序列转换为纯\n，
// 包括在多行字段值中，因此返回的数据不依赖于输入文件使用的行结束约定。
type Reader struct {
	// 逗号是字段分隔符。
	// NewReader将其设置为逗号（'，'）。
	// 逗号必须是有效的符文，并且不能是\r\n、
	// 或Unicode替换字符（0xFFFD）。
	Comma rune

	// 注释，如果不是0，则为注释字符。以
	// 注释字符开头且前面没有空格的行将被忽略。
	// 带前导空格注释字符成为
	// 字段的一部分，即使TrimLeadingSpace为true。
	// 注释必须是有效的符文，并且不能是\r\n、
	// 或Unicode替换字符（0xFFFD）。
	// 它也不能等于逗号。
	Comment rune

	// FieldsPerRecord是每个记录的预期字段数。
	// 如果FieldsPerRecord为正数，则Read要求每条记录具有给定数量的字段。如果FieldsPerRecord为0，Read将其设置为
	// 第一条记录中的字段数，因此未来的记录必须
	// 具有相同的字段计数。如果FieldsPerRecord为负数，则不进行任何检查，并且记录的字段数可能不定。
	FieldsPerRecord int

	// 如果LazyQuotes为真，则引号可能会出现在未加引号的字段中，而
	// 非双引号可能会出现在加引号的字段中。
	LazyQuotes bool

	// 如果trimpleadingspace为true，则忽略字段中的前导空格。
	// 即使字段分隔符逗号是空白，也会执行此操作。
	TrimLeadingSpace bool

	// ReusereRecord控制对Read的调用是否会返回共享
	// 上一次调用返回的性能切片的备份数组的切片。
	// 默认情况下，每次对Read的调用都会返回调用者新分配的内存。
	ReuseRecord bool

	TrailingComma bool // 已弃用：不再使用。

	r *bufio.Reader

	// numLine是CSV文件中正在读取的当前行。
	numLine int

	// rawBuffer是一个仅由readLine方法使用的行缓冲区。
	rawBuffer []byte

	// recordBuffer一个接一个地保存未替换的字段。
	// 可以使用fieldIndexes中的索引来访问字段。
	// 例如，对于'a，“b”，“c”“d”，E`，recordBuffer将包含'abc'de`
	// 字段索引将包含索引[1,2,5,6]。
	recordBuffer []byte

	// 字段索引是recordBuffer内字段的索引。
	// 第i个字段结束于偏移字段索引[i]在记录缓冲区中。
	fieldIndexes []int

	// fieldPositions是读取返回的最后一条记录的字段位置索引。
	fieldPositions []position

	// lastRecord是一个记录缓存，仅在ReuseRecord==true时使用。
	lastRecord []string
}

// NewReader返回一个从r读取的新读卡器。
func NewReader(r io.Reader) *Reader {
	return &Reader{
		Comma: ',',
		r:     bufio.NewReader(r),
	}
}

// Read从r读取一条记录（一个字段片段）。
// 如果该记录的字段数超出预期，
// Read返回该记录以及错误ErrFieldCount。
// 除此之外，Read始终返回非零
// 记录或非零错误，但不能同时返回两者。
// 如果没有要读取的数据，read返回nil，io。EOF。
// 如果ReuseRecord为true，则返回的片可能会在多次读取调用之间共享。
func (r *Reader) Read() (record []string, err error) {
	if r.ReuseRecord {
		record, err = r.readRecord(r.lastRecord)
		r.lastRecord = record
	} else {
		record, err = r.readRecord(nil)
	}
	return record, err
}

// FieldPos返回与
// 最近一次
// 返回的切片中给定索引对应的字段开头的行和列。行和列的编号从1开始；
// 列以字节计，而不是符文。
// 
// 如果使用越界索引调用此函数，则会出现恐慌。
func (r *Reader) FieldPos(field int) (line, column int) {
	if field < 0 || field >= len(r.fieldPositions) {
		panic("out of range index passed to FieldPos")
	}
	p := &r.fieldPositions[field]
	return p.line, p.col
}

// pos保存当前行中某个字段的位置。
type position struct {
	line, col int
}

// ReadAll读取r中所有剩余的记录。
// 每个记录都是一个字段片。
// 成功的调用返回err==nil，而不是err==io。EOF。因为ReadAll是
// 定义为在EOF之前读取，所以它不会将文件结尾视为要报告的错误。
func (r *Reader) ReadAll() (records [][]string, err error) {
	for {
		record, err := r.readRecord(nil)
		if err == io.EOF {
			return records, nil
		}
		if err != nil {
			return nil, err
		}
		records = append(records, record)
	}
}

// readLine读取下一行（带尾随的尾随行）。
// 如果命中EOF时没有尾随的端点，则将忽略它。
// 如果读取了一些字节，则错误永远不会是io。EOF。
// 结果只在下次调用readLine之前有效。
func (r *Reader) readLine() ([]byte, error) {
	line, err := r.r.ReadSlice('\n')
	if err == bufio.ErrBufferFull {
		r.rawBuffer = append(r.rawBuffer[:0], line...)
		for err == bufio.ErrBufferFull {
			line, err = r.r.ReadSlice('\n')
			r.rawBuffer = append(r.rawBuffer, line...)
		}
		line = r.rawBuffer
	}
	if len(line) > 0 && err == io.EOF {
		err = nil
		// 为了向后兼容，请在EOF之前删除尾随。
		if line[len(line)-1] == '\r' {
			line = line[:len(line)-1]
		}
	}
	r.numLine++
	// 在所有输入行上将\r\n规范化为\n。
	if n := len(line); n >= 2 && line[n-2] == '\r' && line[n-1] == '\n' {
		line[n-2] = '\n'
		line = line[:n-1]
	}
	return line, err
}

// lengthNL报告尾随符的字节数\n。
func lengthNL(b []byte) int {
	if len(b) > 0 && b[len(b)-1] == '\n' {
		return 1
	}
	return 0
}

// nextRune返回b或utf8中的下一个符文。RuneError。
func nextRune(b []byte) rune {
	r, _ := utf8.DecodeRune(b)
	return r
}

func (r *Reader) readRecord(dst []string) ([]string, error) {
	if r.Comma == r.Comment || !validDelim(r.Comma) || (r.Comment != 0 && !validDelim(r.Comment)) {
		return nil, errInvalidDelim
	}

	// 读取行（自动跳过过去的空行和任何注释）。
	var line []byte
	var errRead error
	for errRead == nil {
		line, errRead = r.readLine()
		if r.Comment != 0 && nextRune(line) == r.Comment {
			line = nil
			continue // 跳过注释行
		}
		if errRead == nil && len(line) == lengthNL(line) {
			line = nil
			continue // 跳过空行
		}
		break
	}
	if errRead == io.EOF {
		return nil, errRead
	}

	// 解析记录中的每个字段。
	var err error
	const quoteLen = len(`"`)
	commaLen := utf8.RuneLen(r.Comma)
	recLine := r.numLine // 记录的起始行
	r.recordBuffer = r.recordBuffer[:0]
	r.fieldIndexes = r.fieldIndexes[:0]
	r.fieldPositions = r.fieldPositions[:0]
	pos := position{line: r.numLine, col: 1}
parseField:
	for {
		if r.TrimLeadingSpace {
			i := bytes.IndexFunc(line, func(r rune) bool {
				return !unicode.IsSpace(r)
			})
			if i < 0 {
				i = len(line)
				pos.col -= lengthNL(line)
			}
			line = line[i:]
			pos.col += i
		}
		if len(line) == 0 || line[0] != '"' {
			// 非引号字符串字段
			i := bytes.IndexRune(line, r.Comma)
			field := line
			if i >= 0 {
				field = field[:i]
			} else {
				field = field[:len(field)-lengthNL(field)]
			}
			// 检查以确保字段中不出现引号。
			if !r.LazyQuotes {
				if j := bytes.IndexByte(field, '"'); j >= 0 {
					col := pos.col + j
					err = &ParseError{StartLine: recLine, Line: r.numLine, Column: col, Err: ErrBareQuote}
					break parseField
				}
			}
			r.recordBuffer = append(r.recordBuffer, field...)
			r.fieldIndexes = append(r.fieldIndexes, len(r.recordBuffer))
			r.fieldPositions = append(r.fieldPositions, pos)
			if i >= 0 {
				line = line[i+commaLen:]
				pos.col += i + commaLen
				continue parseField
			}
			break parseField
		} else {
			// 带引号的字符串字段
			fieldPos := pos
			line = line[quoteLen:]
			pos.col += quoteLen
			for {
				i := bytes.IndexByte(line, '"')
				if i >= 0 {
					// 点击下一个引号。
					r.recordBuffer = append(r.recordBuffer, line[:i]...)
					line = line[i+quoteLen:]
					pos.col += i + quoteLen
					switch rn := nextRune(line); {
					case rn == '"':
						// /``序列（附加引号）。
						r.recordBuffer = append(r.recordBuffer, '"')
						line = line[quoteLen:]
						pos.col += quoteLen
					case rn == r.Comma:
						// /`，`sequence（字段结束）。
						line = line[commaLen:]
						pos.col += commaLen
						r.fieldIndexes = append(r.fieldIndexes, len(r.recordBuffer))
						r.fieldPositions = append(r.fieldPositions, fieldPos)
						continue parseField
					case lengthNL(line) == len(line):
						// /`\n`sequence（行结束）。
						r.fieldIndexes = append(r.fieldIndexes, len(r.recordBuffer))
						r.fieldPositions = append(r.fieldPositions, fieldPos)
						break parseField
					case r.LazyQuotes:
						// /``序列（裸引号）。
						r.recordBuffer = append(r.recordBuffer, '"')
					default:
						// /``序列（无效的非转义引号）。
						err = &ParseError{StartLine: recLine, Line: r.numLine, Column: pos.col - quoteLen, Err: ErrQuote}
						break parseField
					}
				} else if len(line) > 0 {
					// 点击行尾（复制到目前为止的所有数据）。
					r.recordBuffer = append(r.recordBuffer, line...)
					if errRead != nil {
						break parseField
					}
					pos.col += len(line)
					line, errRead = r.readLine()
					if len(line) > 0 {
						pos.line++
						pos.col = 1
					}
					if errRead == io.EOF {
						errRead = nil
					}
				} else {
					// 文件突然结束（EOF或错误）。
					if !r.LazyQuotes && errRead == nil {
						err = &ParseError{StartLine: recLine, Line: pos.line, Column: pos.col, Err: ErrQuote}
						break parseField
					}
					r.fieldIndexes = append(r.fieldIndexes, len(r.recordBuffer))
					r.fieldPositions = append(r.fieldPositions, fieldPos)
					break parseField
				}
			}
		}
	}
	if err == nil {
		err = errRead
	}

	// 创建一个字符串并从中创建切片。
	// 将字段的内存固定在一起，但只分配一次。
	str := string(r.recordBuffer) // 转换为字符串一次以批量分配
	dst = dst[:0]
	if cap(dst) < len(r.fieldIndexes) {
		dst = make([]string, len(r.fieldIndexes))
	}
	dst = dst[:len(r.fieldIndexes)]
	var preIdx int
	for i, idx := range r.fieldIndexes {
		dst[i] = str[preIdx:idx]
		preIdx = idx
	}

	// 检查或更新每个记录的预期字段。
	if r.FieldsPerRecord > 0 {
		if len(dst) != r.FieldsPerRecord && err == nil {
			err = &ParseError{
				StartLine: recLine,
				Line:      recLine,
				Column:    1,
				Err:       ErrFieldCount,
			}
		}
	} else if r.FieldsPerRecord == 0 {
		r.FieldsPerRecord = len(dst)
	}
	return dst, err
}
